package com.bilifuture.statemachine.machine;

import com.bilifuture.statemachine.action.Action;
import com.bilifuture.statemachine.exception.StateMachineException;
import com.bilifuture.statemachine.interceptor.StateMachineInterceptor;
import com.bilifuture.statemachine.support.StateMachineConstant;
import com.bilifuture.statemachine.transition.Transition;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.springframework.core.io.Resource;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import java.io.IOException;
import java.io.InputStream;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @author bilifuture
 * @description: 状态机构建工具类
 * @date 2022-02-23
 */
@Slf4j
public class StateMachineBuilder {
    
    /**
     * Bean名称
     */
    private static final String BEAN_NAME = "beanName";
    
    private static final String INTERCEPTORS = "interceptors";
    
    private static final String INTERCEPTOR = "interceptor";
    
    
    private static final String TRANSITIONS = "transitions";
    
    private static final String TRANSITION = "transition";
    
    private static final String EVENT = "event";
    
    private static final String TARGET = "target";
    
    private static final String ACTION_BEAN_NAME = "actionBeanName";
    
    /**
     * ROOT_ELEMENT_NAME
     */
    private static final String ROOT_ELEMENT_NAME = "stateMachines";
    
    /**
     * STATE_MACHINE
     */
    private static final String STATE_MACHINE = "stateMachine";
    
    private static final String STATE_MACHINE_NAME = "name";
    
    private static final String STATE_MACHINE_DESC = "desc";
    
    /**
     * 状态机
     */
    private StateMachine stateMachine;
    
    /**
     * 状态机保持器
     */
    private StateMachineHolder stateMachineHolder;
    
    
    
    public StateMachineBuilder(StateMachine stateMachine) {
        this.stateMachine = stateMachine;
        this.stateMachineHolder = stateMachine.getStateMachineHolder();
    }
    
    public void build() throws ParserConfigurationException, IOException, SAXException {
        //1、获取配置文件位置
        String configLocation = stateMachine.getConfigLocation();
        if(StringUtils.isEmpty(configLocation)){
            log.info("未配置configLocation，使用默认的配置：{}",StateMachineConstant.DEFAULT_CONFIG_FILE);
            configLocation = StateMachineConstant.DEFAULT_CONFIG_FILE;
        }
        
        //2、加载配置文件为document
        Document document = loadConfig(configLocation);
        
        //3、解析配置文件
        Element rootElement = document.getDocumentElement();
        if(ROOT_ELEMENT_NAME.equals(rootElement.getTagName())){
            buildStateMachine(rootElement,stateMachineHolder);
        }
    }
    
    private void buildStateMachine(Element rootElement, StateMachineHolder stateMachineHolder) {
        //1、解析全局拦截器
        buildGlobalInterceptor(rootElement,stateMachineHolder);
        //2、解析所有的状态机
        buildStateMachineList(rootElement,stateMachineHolder);
    }
    
    /**
     * 构建状态机列表
     *
     * @param rootElement        根元素
     * @param stateMachineHolder 状态机保持器
     */
    private void buildStateMachineList(Element rootElement, StateMachineHolder stateMachineHolder) {
        NodeList stateMachineList = rootElement.getElementsByTagName(STATE_MACHINE);
        
        Map<String, StateMachineConfig> stateMachineMap = new ConcurrentHashMap<>();
        for (int i = 0; i < stateMachineList.getLength(); i++) {
            StateMachineConfig stateMachineConfig = new StateMachineConfig();
            
            Element stateMachineEle = (Element) stateMachineList.item(i);
            String name = stateMachineEle.getAttribute(STATE_MACHINE_NAME);
            String desc = stateMachineEle.getAttribute(STATE_MACHINE_DESC);
    
            stateMachineConfig.setName(name);
            stateMachineConfig.setDesc(desc);
            
            //1、解析所有状态机级别拦截器
            List<StateMachineInterceptor> stateMachineInterceptors = buildInterceptorList(stateMachineEle);
            stateMachineConfig.setInterceptorList(stateMachineInterceptors);
    
            //2、解析当前状态机下的所有Transition
            Set<Transition> transitionList = buildTransitionList(stateMachineEle, stateMachineHolder);
            
            stateMachineConfig.setTransitionSet(transitionList);
    
            stateMachineMap.put(name, stateMachineConfig);
        }
        stateMachineHolder.addStateMachine(stateMachineMap);
    }
    
    /**
     * 构建状态转换配置列表
     *
     * @param stateMachineEle    状态机ELEE
     * @param stateMachineHolder 状态机保持器
     * @return {@link List}<{@link Transition}>
     */
    private Set<Transition> buildTransitionList(Element stateMachineEle, StateMachineHolder stateMachineHolder){
        Set<Transition> transitionList = new HashSet<>();
        NodeList transitionsNodeList = stateMachineEle.getElementsByTagName(TRANSITIONS);
        if(Objects.nonNull(transitionsNodeList) && transitionsNodeList.getLength() >0){
            if(transitionsNodeList.getLength() == 1){
                Element transitionsEle = (Element) transitionsNodeList.item(0);
                NodeList transitionNodeList = transitionsEle.getElementsByTagName(TRANSITION);
                for (int i = 0; i < transitionNodeList.getLength(); i++) {
                    Transition transition = new Transition();
                    
                    Element item = (Element)transitionNodeList.item(i);
                    String event = item.getAttribute(EVENT);
                    String target = item.getAttribute(TARGET);
                    String actionBeanName = item.getAttribute(ACTION_BEAN_NAME);
                    Action action = (Action) stateMachine.getApplicationContext().getBean(actionBeanName);
                    //解析当前Transition下的所有拦截器集合
                    List<StateMachineInterceptor> interceptorList = buildInterceptorList(item);
                    
                    transition.setAction(action);
                    transition.setEvent(event);
                    transition.setTarget(target);
                    transition.setInterceptorList(interceptorList);
    
                    transitionList.add(transition);
                }
            }else {
                throw new StateMachineException("<transitions>标签出现了多个");
            }
        }
        return transitionList;
    }
    
    /**
     * 建造全局拦截器
     *
     * @param rootElement        根元素
     * @param stateMachineHolder 状态机保持器
     */
    private void buildGlobalInterceptor(Element rootElement, StateMachineHolder stateMachineHolder) {
        List<StateMachineInterceptor> stateMachineInterceptors = buildInterceptorList(rootElement);
        stateMachineHolder.addInterceptorList(stateMachineInterceptors);
    }
    
    private List<StateMachineInterceptor> buildInterceptorList(Element element){
        List<StateMachineInterceptor> interceptorList = new ArrayList<StateMachineInterceptor>();
        NodeList interceptors = element.getElementsByTagName(INTERCEPTORS);
        if(Objects.nonNull(interceptors) && interceptors.getLength() >0){
            if(interceptors.getLength() == 1){
                Element interceptorsEle = (Element) interceptors.item(0);
                NodeList interceptorNodeList = interceptorsEle.getElementsByTagName(INTERCEPTOR);
                for (int i = 0; i < interceptorNodeList.getLength(); i++) {
                    Element item = (Element)interceptorNodeList.item(i);
                    String beanName = item.getAttribute(BEAN_NAME);
                    StateMachineInterceptor globalInterceptor = (StateMachineInterceptor) stateMachine.getApplicationContext().getBean(beanName);
                    interceptorList.add(globalInterceptor);
                }
            }else{
                throw new StateMachineException("<interceptors>标签出现了多个");
            }
        }
        return interceptorList;
    }
    
    /**
     * 加载配置
     *
     * @param configLocation 配置位置
     * @return {@link Document}
     * @throws IOException                  IOException
     * @throws ParserConfigurationException 解析器配置异常
     * @throws SAXException                 SaxException
     */
    private Document loadConfig(String configLocation) throws IOException, ParserConfigurationException, SAXException {
        DocumentBuilder documentBuilder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
        Document document =null;
        if(configLocation.startsWith("classpath:")){
            Resource resource = stateMachine.getApplicationContext().getResource(configLocation);
            document = documentBuilder.parse(resource.getFile());
        } else {
            InputStream stream = stateMachine.getApplicationContext().getClassLoader().getResourceAsStream(configLocation);
            document = documentBuilder.parse(stream);
        }
        return document;
    }
}
